#include <device.h>
#include <errno.h>
#include <string.h>

/*
extern struct device __devices_start[], __devices_end[];

#define for_each_device(p)                                              \
    for (p = __devices_start; p < __devices_end; p++)       

#ifndef __TOP_HALF__ 

extern struct driver __drivers_start[], __drivers_end[];

#define for_each_driver(p)                                              \
    for (p = __drivers_start; p < __drivers_end; p++)       

#endif
*/

#ifdef __TOP_HALF__

struct device *device_lookup_by_id(unsigned int id, int index)
{
	struct device *dev;

	for_each_device(dev) {
		if ((dev->id == id) && (dev->index == index)) 
			return dev;
	}

	return ERR_PTR(-ENODEV);
}

#else

struct device *device_lookup_by_name(char *name)
{
    struct device *dev;  

	if (!name) 
		return ERR_PTR(-EINVAL);

    for_each_device(dev) {    
        if (strcmp(dev->name, name) == 0) 
            return dev;  
    }

    return ERR_PTR(-ENODEV);  
}

struct device *device_lookup(dev_t devt)
{
    struct device *dev;

    for_each_device(dev) {    
        if (dev->devt == devt) 
			return dev;
    }

    return ERR_PTR(-ENODEV);  
}

int device_open(struct device *dev, unsigned int oflags) 
{
    struct driver *drv= device_to_driver(dev);
	int ret = 0;

	if (IS_ERR(dev) || (dev == NULL))
		return -EINVAL;
		
	if (dev->refcount++ != 0) 
		return 0;

	oflags &= DEV_OFLAGS_MASK;
	oflags &= dev->flags;
	if (dev_flags_is_can_seek(dev->flags))
		dev_oflags_set_seek(oflags);
	if (dev_oflags_is_nonblock(oflags))
		dev_oflags_clr_sync(oflags);

    dev->oflags = oflags;

    if (drv->ops->open)
        ret = drv->ops->open(dev);

	return ret;
}

void device_close(struct device *dev)
{
    struct driver *drv= device_to_driver(dev);

	if (IS_ERR(dev) || (dev == NULL))
		return;

	if (--dev->refcount != 0) 
		return;

	if (!dev_flags_is_no_closing_flush(dev->flags)) {
		if (drv->ops->flush)
			drv->ops->flush(dev);
	}

    if (drv->ops->close)
        drv->ops->close(dev);
}

void device_flush(struct device *dev)
{
    struct driver *drv= device_to_driver(dev);

	if (IS_ERR(dev) || (dev == NULL))
		return;

	if (dev->refcount <= 0)
		return;

    if (drv->ops->flush)
        drv->ops->flush(dev);
}

int device_read(struct device *dev, void *buf, unsigned int len)
{
    struct driver *drv= device_to_driver(dev);
	int ret = 0;

	if (IS_ERR(dev) || (dev == NULL) || IS_ERR(buf) || (buf == NULL) || (len == 0))
		return -EINVAL;

	if (dev->refcount <= 0)
		return -EFAULT;

	if (!dev_oflags_is_read(dev->oflags))
		return -EFAULT;

    if (drv->ops->read)
        ret = drv->ops->read(dev, buf, len);
		
	return ret;
}

int device_write(struct device *dev, const void *buf, unsigned int len)
{
    struct driver *drv= device_to_driver(dev);
	int ret = 0;

	if (IS_ERR(dev) || (dev == NULL) || IS_ERR(buf) || (buf == NULL) || (len == 0))
		return -EINVAL;

	if (dev->refcount <= 0)
		return -EFAULT;

	if (!dev_oflags_is_write(dev->oflags))
		return -EFAULT;

    if (drv->ops->write)
        ret = drv->ops->write(dev, buf, len);
	
	return ret;
}

loff_t device_llseek(struct device *dev, loff_t offset, unsigned int origin)
{
    struct driver *drv= device_to_driver(dev);
	loff_t ret = 0;

	if (IS_ERR(dev) || (dev == NULL) || (origin > DEV_SEEK_END))
		return -EINVAL;

	if (dev->refcount <= 0)
		return -EFAULT;

	if (!dev_oflags_is_seek(dev->oflags))
        return -EFAULT;

    if (drv->ops->seek)
        ret = drv->ops->seek(dev, offset, origin);
		
	return ret;
}

int device_ioctl(struct device *dev, unsigned int cmd, unsigned int arg)
{
    struct driver *drv= device_to_driver(dev);
	int ret = 0;

	if (IS_ERR(dev) || (dev == NULL))
		return -EINVAL;

	if (dev->refcount <= 0)
		return -EFAULT;

    if (drv->ops->ioctl)
        ret = drv->ops->ioctl(dev, cmd, arg);
	
	return ret;
}

int register_driver(struct driver *drv)
{
    struct device *dev;
    int ret, found = 0;

	if (IS_ERR(drv) || (drv == NULL) || (drv->min_devt > drv->max_devt))
		return -EINVAL;

	for_each_device(dev) {
		if (((drv->name) && (strcmp(dev->name, drv->name) == 0))
			|| ((dev->devt >= drv->min_devt) && (dev->devt <= drv->max_devt))
			|| ((drv->ops->match) && (!drv->ops->match(dev, drv)))) {
			ret = 0;
			if (drv->ops->probe)
				ret = drv->ops->probe(dev, drv);
			if (!ret) {
				dev->drv = drv;
				list_add_tail(&dev->n_dev_list, &drv->dev_list);
				found = 1;
			}				
		}
	}
	
    if (!found)
        return -ENODEV;

    return 0;
}

#endif

